/* jshint forin:true, noarg:true, noempty:true, eqeqeq:true, boss:true, undef:true, curly:true, browser:true, jquery:true */
/*
 * jQuery MultiSelect UI Widget Filtering Plugin 1.5pre
 * Copyright (c) 2012 Eric Hynds
 *
 * http://www.erichynds.com/jquery/jquery-ui-multiselect-widget/
 *
 * Depends:
 *   - jQuery UI MultiSelect widget
 *
 * Dual licensed under the MIT and GPL licenses:
 *   http://www.opensource.org/licenses/mit-license.php
 *   http://www.gnu.org/licenses/gpl.html
 *
 */
(function($) {
    var rEscape = /[\-\[\]{}()*+?.,\\\^$|#\s]/g;

    $.widget('ech.multiselectfilter', {

        options: {
            label: 'Filter:',
            width: null, /* override default width set in css file (px). null will inherit */
            placeholder: 'Enter keywords',
            autoReset: false
        },

        _create: function() {
            var opts = this.options;
            var elem = $(this.element);
            var  nodeName = this.element[0].nodeName.toLowerCase(),
                  isTextarea = nodeName === "textarea",
                  isInput = nodeName === "input";

            this.valueMethod = this.element[isTextarea || isInput ? "val" : "text"];

            // get the multiselect instance
            var instance = (this.instance = (elem.data('echMultiselect') || elem.data("multiselect") || elem.data("ech-multiselect")));

            // store header; add filter class so the close/check all/uncheck all links can be positioned correctly
            var header = (this.header = instance.menu.find('.ui-multiselect-header').addClass('ui-multiselect-hasfilter'));
            // wrapper elem
            var wrapper = (this.wrapper = $('<div class="ui-multiselect-filter">' + (opts.label.length ? opts.label : '') + '<input placeholder="' + opts.placeholder + '" type="search"' + (/\d/.test(opts.width) ? 'style="width:' + opts.width + 'px"' : '') + ' />' + '</div>').prependTo(this.header));

            // reference to the actual inputs
            this.inputs = instance.menu.find('input[type="checkbox"], input[type="radio"]');

            // build the input box
            this.input = wrapper.find('input').bind({
                keydown: function(e) {
                    // prevent the enter key from submitting the form / closing the widget
                    if(e.which === 13) {
                        e.preventDefault();
                    }
                },
                keyup: $.proxy(this._delayhandler, this),
                //click: $.proxy(this._handler, this),
                //input: $.proxy(this._clear,this)
            });

            // search button
            this.button = $("<button type='button'>&nbsp;</button>")
                           .attr("tabIndex", -1)
                           .attr("title", "Show Top 50 Items from the start")
                           .insertAfter(this.input)
                           .button({
                               icons: {
                                   primary: "ui-icon-search"
                               },
                               text: false
                           })
                           .addClass("ui-button-icon ")
                           .bind({
                                mousedown : function () { },
                                click: $.proxy(function (a, b, c) {
                                    this.input.val('');
                                    this._delayhandler(a,b,c);
                                    },this)
                                });

      // cache input values for searching
      this.updateCache();

      // rewrite internal _toggleChecked fn so that when checkAll/uncheckAll is fired,
      // only the currently filtered elements are checked
      instance._toggleChecked = function(flag, group) {
        var $inputs = (group && group.length) ?  group : this.labels.find('input');
        var _self = this;

        // do not include hidden elems if the menu isn't open.
        var selector = _self._isOpen ?  ':disabled, :hidden' : ':disabled';

        $inputs = $inputs
          .not(selector)
          .each(this._toggleState('checked', flag));

        // update text
        this.update();

        // gather an array of the values that actually changed
        var values = $inputs.map(function() {
          return this.value;
        }).get();

        // select option tags
        this.element.find('option').filter(function() {
          if(!this.disabled && $.inArray(this.value, values) > -1) {
            _self._toggleState('selected', flag).call(this);
          }
        });

        // trigger the change event on the select
        if($inputs.length) {
          this.element.trigger('change');
        }
      };

      // rebuild cache when multiselect is updated
      var doc = $(document).bind('multiselectrefresh.' + instance._namespaceID, $.proxy(function () {
        clearTimeout(this.filtering);
        this.updateCache();
        this._handler();
      }, this));

      // automatically reset the widget on close?
      if(this.options.autoReset) {
        doc.bind('multiselectclose.'+ instance._namespaceID, $.proxy(this._reset, this));
      }
    },

    _delay: function (handler, delay) {
        function handlerProxy() {
            return (typeof handler === "string" ? instance[handler] : handler)
				.apply(instance, arguments);
        }
        var instance = this;
        return setTimeout(handlerProxy, delay || 0);
    },
    _clear: function (e) {
        var term = $.trim(this.input[0].value.toLowerCase());
        if (term == "") { this._delayhandler(e); }
    },
    _value: function () {
        return this.valueMethod.apply(this.element, arguments);
    },
    _response: function(result) {
        //$(this.instance).multiselect('refresh');
    },
    _asynchandler: function (e) {
        if (typeof this.options.source === "function") {
            var term = $.trim(this.input[0].value.toLowerCase());
            this.options.source(term, $.proxy(this._response, this));
        }
        else this._handler(e);
    },
    _delayhandler: function (e) {
        clearTimeout(this.filtering);
        this.filtering = this._delay(this._asynchandler, this.options.delay);
    },

    // thx for the logic here ben alman
    _handler: function(e) {
      var term = $.trim(this.input[0].value.toLowerCase()),

      // speed up lookups
      rows = this.rows, inputs = this.inputs, cache = this.cache;
      if(!term) {
        rows.show();
      } else {
        rows.hide();

        var regex = new RegExp(term.replace(rEscape, "\\$&"), 'gi');

        this._trigger("filter", e, $.map(cache, function(v, i) {
          if(v.search(regex) !== -1 || rows.eq(i).parent('attr','show')) {
            rows.eq(i).show();
            return inputs.get(i);
          }

          return null;
        }));
      }

      // show/hide optgroups
      this.instance.menu.find(".ui-multiselect-optgroup-label").each(function() {
        var $this = $(this);
        var isVisible = $this.nextUntil('.ui-multiselect-optgroup-label').filter(function() {
          return $.css(this, "display") !== 'none';
        }).length;

        $this[isVisible ? 'show' : 'hide']();
      });
    },

    _reset: function() {
      clearTimeout(this.filtering);
      this.input.val('').trigger('keyup');
    },

    updateCache: function() {
      // each list item
      this.rows = this.instance.menu.find(".ui-multiselect-checkboxes li:not(.ui-multiselect-optgroup-label)");

      // cache
      this.cache = this.element.children().map(function() {
        var elem = $(this);

        // account for optgroups
        if(this.tagName.toLowerCase() === "optgroup") {
          elem = elem.children();
        }

        return elem.map(function() {
          return this.innerHTML.toLowerCase();
        }).get();
      }).get();
    },

    widget: function() {
      return this.wrapper;
    },

    reload: function () {
        this._asynchandler();
    },
    destroy: function () {
      clearTimeout(this.filtering);
      $.Widget.prototype.destroy.call(this);
      this.input.val('').trigger("keyup");
      this.wrapper.remove();
    }
  });

})(jQuery);
